package apps.sim.http;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import apps.sim.Index;
import apps.sim.action.handler.HandlerFactory;
import apps.sim.core.IHandler;
import apps.sim.core.IHandler.Action;
import apps.sim.core.IHandler.Config;
import apps.sim.core.IHandler.IHandlerConfig;
import apps.sim.core.IHandler.Util;

import com.sun.net.httpserver.HttpExchange;

public class HttpHandlerConfLookup extends HttpBaseHandler {

	protected void execute(HttpExchange exchange) throws Exception {
		final Map<String, String> params = extractParams(exchange.getRequestURI());
		final String save = params.get("save");
		final String invoke = params.get("invoke");
		if (save != null && save.equalsIgnoreCase("true")) {
			save(exchange, params);
			successResponse(exchange, new ByteArrayInputStream("<center><br/><br/>Data Saved...</center>".getBytes()));
		} else if (invoke != null && invoke.equalsIgnoreCase("true")) {
			try {
				final String value = invoke(exchange, params);
				successResponse(
						exchange,
						new ByteArrayInputStream(
								("<center><br/><br/><h2>Method Invoked...</h2><br/>Returned: " + value + "<br/><br/><br/><a href=\"javascript:history.back()\">Back</a></center>")
										.getBytes()));
			} catch (Exception e) {
				successResponse(exchange, Util.getAsString(e));
			}
		} else {
			view(exchange, params);
		}

	}

	private String invoke(HttpExchange exchange, Map<String, String> params) throws Exception {
		final String method = params.get("method");
		final String handler = params.get("handler");
		final List<String> argTypes = getParams(params, "argType");
		final List<String> argValues = getParams(params, "argValue");
		final IHandlerConfig handlerConfiguration = HandlerFactory.getInstance().getHandler(handler).getHandlerConfiguration();
		final Class<?>[] classes = toClasses(argTypes);
		final Method methodToInvoke = handlerConfiguration.getClass().getMethod(method, classes);
		final Object[] values = toValues(classes, argValues);
		final String invoke = (String) methodToInvoke.invoke(handlerConfiguration, values);
		return new String(invoke);
	}

	private void save(HttpExchange exchange, Map<String, String> params) throws Exception {
		final String handler = params.get("handler");
		if (handler != null) {
			final IHandler handlerIns = HandlerFactory.getInstance().getRootHandler(handler);
			final IHandlerConfig configuration = handlerIns.getHandlerConfiguration();
			final String setters = params.get("setters");
			final int nums = Integer.parseInt(setters);
			for (int index = 0; index < nums; index++) {
				final String name = params.get("method" + index);
				final List<String> argTypes = getParams(params, "argType" + index);
				final List<String> argValues = getParams(params, "argValue" + index);
				Collections.sort(argTypes);
				Collections.sort(argValues);
				final Class<?>[] classes = toClasses(argTypes);
				Method method = null;
				try {
					method = configuration.getClass().getMethod(name, classes);
				} catch (NoSuchMethodException e) {
					method = configuration.getClass().getMethod(name, asPrimitives(classes));
				}
				method.invoke(configuration, toValues(classes, argValues));
			}

		}
	}

	private Class<?>[] asPrimitives(Class<?>[] classes) {
		for (int index = 0; index < classes.length; index++) {
			final Class<?> class1 = WRAPPERS_TO_PRIMITIVES.get(classes[index]);
			classes[index] = class1 != null ? class1 : classes[index];
		}
		return classes;
	}

	private void view(HttpExchange exchange, final Map<String, String> params) throws Exception, IOException {
		final String clazz = params.get("handler");
		final IHandler handler = HandlerFactory.getInstance().getHandler(clazz);
		final IHandlerConfig configuration = handler.getHandlerConfiguration();
		if (configuration != null) {
			final List<List<Method>> settersAndActions = operations(configuration.getClass());
			final String content = createHTML(settersAndActions, clazz, configuration);
			final String html = getFromFile(content);
			successResponse(exchange, new ByteArrayInputStream(html.getBytes()));
		} else {
			successResponse(exchange, new ByteArrayInputStream("No configurations found for the handler".getBytes()));
		}
	}

	private String getFromFile(String content) throws IOException {
		final InputStream resourceAsStream = Index.class.getResourceAsStream("http/page/conf.html");
		final StringWriter stringWriter = new StringWriter();
		int c;
		while ((c = resourceAsStream.read()) != -1) {
			stringWriter.write(c);
		}
		final String string = stringWriter.toString();
		return string.replaceFirst("_CONTENT_", content);
	}

	private String createHTML(List<List<Method>> settersAndActions, String clazz, IHandlerConfig configuration) {
		final String settersHtml = settersHtml(clazz, configuration, settersAndActions.get(0));
		final String actionsHtml = actionsHtml(clazz, configuration, settersAndActions.get(1));
		return settersHtml + actionsHtml;
	}

	private String actionsHtml(String clazz, IHandlerConfig configuration, List<Method> actions) {
		if (actions.size() == 0) {
			return "";
		}
		final StringBuilder builder = new StringBuilder();
		builder.append("<b>&nbsp;&nbsp; Actions: Click on Invoke to execute</b>");
		builder.append("<hr/><table style=\"width: 100%;background-color: #eeeeee;\">");
		for (int indx = 0; indx < actions.size(); indx++) {
			Method method = actions.get(indx);
			builder.append("\n\t<tr><td style=\"text-align:right\"><b>");
			final String name = method.getName();
			builder.append(name);
			final Action action = method.getAnnotation(Action.class);
			builder.append("( ");
			final Class<?>[] parameterTypes = method.getParameterTypes();
			for (int index = 0; index < parameterTypes.length; index++) {
				Class<?> class1 = parameterTypes[index];
				if (index != 0) {
					builder.append(", ");
				}
				builder.append("<input style=\"height: 25px;width: 50px\" type=\"text\"  name=\"argValue" + index + "\"/> :" + class1.getName());
				if (class1.isPrimitive()) {
					class1 = PRIMITIVES_TO_WRAPPERS.get(class1);
				}
				builder.append("<input type=\"hidden\" value=\"" + class1.getCanonicalName() + "\" name=\"argType" + index + "\"/>");
			}
			builder.append(" );</b>");
			builder.append(" <p style=\"font-weight: normal; font-size: 9px\">[");
			builder.append(action.descrition());
			builder.append("]</p>");
			builder.append("\n\t\t</td>");
			builder.append("\n\t\t<td style=\"width: 50px\"></td>");
			builder.append("\n\t<td style=\"width: 150px\">");
			builder.append("<form action=\"/simulate/global/hander/conf\" method=\"get\" style=\"background-color: #eeeeee\" >");
			builder.append("<input type=\"hidden\" value=\"true\" name=\"invoke\"/>");
			builder.append("<input type=\"hidden\" value=\"" + clazz + "\" name=\"handler\"/>");
			builder.append("<input type=\"hidden\" value=\"" + actions.size() + "\" name=\"numberOfParams\"/>");
			builder.append("<input type=\"hidden\" value=\"" + name + "\" name=\"method\"/>");
			builder.append("<a class=\"g-button g-button-share\" href=\"javascript:document.forms[" + (indx + 1) + "].submit();\">Invoke</a>");
			builder.append("\n\t</td></tr>");
			builder.append("<tr><td colspan=\"3\"><hr/></td></tr>");
			builder.append("</form>");
		}
		builder.append("</table>");
		return builder.toString();
	}

	private String settersHtml(String clazz, IHandlerConfig configuration, final List<Method> setters) {
		final StringBuilder builder = new StringBuilder();
		builder.append("<form action=\"/simulate/global/hander/conf\" method=\"get\"><center>");
		builder.append("<table>");
		builder.append("<input type=\"hidden\" value=\"true\" name=\"save\"/>");
		builder.append("<input type=\"hidden\" value=\"" + clazz + "\" name=\"handler\"/>");
		builder.append("<input type=\"hidden\" value=\"" + setters.size() + "\" name=\"setters\"/>");
		builder.append("<p style=\"font-size: 16px; font-wight: bold\">Configuration for: ");
		builder.append(clazz);
		builder.append("</p>");
		builder.append("<hr/>");
		for (int indx = 0; indx < setters.size(); indx++) {
			Method method = setters.get(indx);
			final String name = method.getName();
			builder.append("\n\t<tr>");
			builder.append("\n\t\t<td align=\"right\" style=\"font-weight: bold; width: 250px\"/>");
			builder.append("<input type=\"hidden\" value=\"" + name + "\" name=\"method" + indx + "\"/>");
			builder.append(extractFieldName(name));
			final Config config = method.getAnnotation(Config.class);
			builder.append("<br/><p style=\"font-weight: normal; font-size: 9px\">");
			builder.append(config.descrition());
			builder.append("</p>");
			builder.append("\n\t\t</td>");
			final Class<?>[] parameterTypes = method.getParameterTypes();
			for (int index = 0; index < parameterTypes.length; index++) {
				Class<?> class1 = parameterTypes[index];
				builder.append("\n\t\t<td>");
				if (class1 == StringBuilder.class) {
					final String str = "\n\t\t\t&nbsp;<textarea style=\"width: 300px; height: 100px\"  name=\"argValue" + indx + "" + index + "\">"
							+ getValue(configuration, method) + "</textarea> [" + class1.getName() + "]";
					builder.append(str);
				} else {
					builder.append("\n\t\t\t&nbsp;<input style=\"width: 300px; height: 25px\" type=\"text\"  name=\"argValue" + indx + "" + index + "\"  value=\""
							+ getValue(configuration, method) + "\"/> [" + class1.getName() + "]");
				}
				if (class1.isPrimitive()) {
					class1 = PRIMITIVES_TO_WRAPPERS.get(class1);
				}
				builder.append("\n\t\t\t<input type=\"hidden\" value=\"" + class1.getCanonicalName() + "\" name=\"argType" + indx + "" + index + "\"/>");
				builder.append("\n\t\t</td>");
			}
			builder.append("\n\t</tr>");
			builder.append("\n\t<tr><td style=\"height: 5px\">&nbsp;</td></tr>");
		}
		builder.append("\n</table>");
		builder.append("<a class=\"g-button g-button-submit\" href=\"javascript:document.forms[0].submit();\">Save</a></center>");
		builder.append("<hr/>");
		builder.append("</form>");
		return builder.toString();
	}

	private String getValue(IHandlerConfig configuration, Method method) {
		final String name = method.getName();
		char[] charArray = "".toCharArray();
		final Class<?>[] parameterTypes = method.getParameterTypes();
		if (parameterTypes.length == 1 && parameterTypes[0] == Boolean.class || parameterTypes[0] == boolean.class) {
			charArray = ("is" + name.substring(3)).toCharArray();
		} else {
			charArray = name.toCharArray();
			charArray[0] = 'g';
		}

		try {
			final Method target = configuration.getClass().getMethod(new String(charArray));
			final Object invoke = target.invoke(configuration);
			final String stringValue = invoke != null ? String.valueOf(invoke) : "";
			return stringValue.replaceAll("\\\\", "/");
		} catch (Exception e) {
			return "";
		}

	}

	private String extractFieldName(String name) {
		final String string = name.substring(name.indexOf("set") + 3);
		String finalString = string;
		Pattern pattern = Pattern.compile("[a-z][A-Z]");
		final Matcher matcher = pattern.matcher(string);
		while (matcher.find()) {
			final String group = matcher.group();
			finalString = finalString.replaceFirst(group, group.charAt(0) + " " + group.charAt(1));

		}
		return finalString;
	}

	@SuppressWarnings("unchecked")
	private List<List<Method>> operations(Class<?> clazz) {
		final Method[] methods = clazz.getMethods();
		final List<Method> setters = new ArrayList<Method>();
		final List<Method> actions = new ArrayList<Method>();
		OUT: for (Method method : methods) {
			if (!method.isSynthetic()) {
				if ((method.getName().startsWith("set") && method.getParameterTypes().length > 0 && method.getAnnotation(Config.class) != null)) {
					setters.add(method);
				} else if (method.getAnnotation(Action.class) != null && method.getReturnType() == String.class) {
					final Class<?>[] parameterTypes = method.getParameterTypes();
					for (Class<?> class1 : parameterTypes) {
						if (class1.isPrimitive()) {
							continue OUT;
						}
					}
					actions.add(method);
				}
			}
		}
		return Arrays.asList(setters, actions);
	}

	private Object[] toValues(Class<?>[] classes, List<String> argValues) {
		List<String> supportedCLasses = new ArrayList<String>();
		supportedCLasses.add(Integer.class.toString());
		supportedCLasses.add(int.class.toString());
		supportedCLasses.add(Boolean.class.toString());
		supportedCLasses.add(boolean.class.toString());
		supportedCLasses.add(Character.class.toString());
		supportedCLasses.add(char.class.toString());
		supportedCLasses.add(Double.class.toString());
		supportedCLasses.add(double.class.toString());
		supportedCLasses.add(Float.class.toString());
		supportedCLasses.add(float.class.toString());

		supportedCLasses.add(File.class.toString());
		supportedCLasses.add(StringBuilder.class.toString());
		try {
			final List<Object> list = new ArrayList<Object>();
			for (int index = 0; index < classes.length; index++) {
				final Class<?> clazz = classes[index];
				final String argValueStr = argValues.get(index);
				final String[] split = argValueStr.split("\\|");
				if (split.length == 2) {
					final String argValue = split[1];
					if (clazz.equals(String.class)) {
						list.add(argValue);
					} else {
						if (argValue != null && !argValue.equals("")) {
							if (clazz.equals(Integer.class) || clazz.equals(int.class)) {
								list.add(new Integer(argValue));
							} else if (clazz.equals(Boolean.class) || clazz.equals(boolean.class)) {
								list.add(new Boolean(argValue));
							} else if (clazz.equals(Character.class) || clazz.equals(char.class)) {
								list.add(new Character(argValue.charAt(0)));
							} else if (clazz.equals(Double.class) || clazz.equals(double.class)) {
								list.add(new Double(argValue));
							} else if (clazz.equals(Float.class) || clazz.equals(float.class)) {
								list.add(new Float(argValue));
							} else if (clazz.equals(File.class)) {
								list.add(new File(argValue));
							} else if (clazz.equals(StringBuilder.class)) {
								list.add(new StringBuilder(argValue));
							} else {
								list.add(clazz.cast(argValue));
							}
						} else {
							list.add(clazz.cast(argValue));
						}
					}
				} else {
					list.add(null);
				}
			}
			return list.toArray();
		} catch (ClassCastException e) {
			throw new RuntimeException(" Casting problem: Supported classes are " + supportedCLasses, e);
		}
	}

	private Class<?>[] toClasses(List<String> argTypes) throws ClassNotFoundException {
		final List<Class<?>> list = new ArrayList<Class<?>>();
		for (String string : argTypes) {
			final String className = string.split("\\|")[1];
			list.add(Class.forName(className));
		}
		return list.toArray(new Class<?>[] {});
	}

	private List<String> getParams(Map<String, String> params, String string) {
		final List<String> list = new ArrayList<String>();
		final Set<Entry<String, String>> entrySet = params.entrySet();
		for (Entry<String, String> entry : entrySet) {
			if (entry.getKey().startsWith(string)) {
				list.add(entry.getKey() + "\\|" + entry.getValue());
			}
		}
		return list;
	}

}